#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include "random.h"
#include "encrypt.h"
#include "config.h"
#include "findhome.h"

#define _ENTROPY_BLOCKS 4
#if _ENTROPY_BLOCKS < 2
#error "You need at least 2 blocks of entropy."
#endif
static int const ENTROPY_BLOCKS = _ENTROPY_BLOCKS;
static uint64_t entropy_pool [_ENTROPY_BLOCKS];
static uint8_t *key = (uint8_t *) &(entropy_pool [_ENTROPY_BLOCKS - 2]);
static uint64_t counter = 0;

/*
 * Loads the entropy pool from disk, if it exists.
 */
void initialize_random (char const * const home)
{
	char random_pool_path [32];
	int fd;
	strncpy (random_pool_path, home, 32);
	strncat (random_pool_path, RANDOM_POOL, 32);
	fd = open (random_pool_path, O_RDONLY);
	if (fd != -1)
	{
		read (fd, entropy_pool, sizeof (entropy_pool));
		close (fd);
	}
}

/*
 * Mixes seed into the entropy pool. 
 */
void seed_random (uint64_t seed)
{
	int i;
	uint64_t block;
	block = entropy_pool [0] ^ seed;
	block_encrypt (key, (uint8_t *) &block, (uint8_t *) &entropy_pool [0]);
	for (i = 1; i < ENTROPY_BLOCKS; i ++)
	{
		block = entropy_pool [i] ^ entropy_pool [i - 1];
		block_encrypt (key, (uint8_t *) &block, (uint8_t *) &entropy_pool [i]);
	}
}

/*
 * Obtains a 64-bit random number.
 */
uint64_t generate_random (void)
{
	uint64_t random;
	counter = counter + 1;
	block_encrypt (key, (uint8_t *) &counter, (uint8_t *) &random);
	return random;
}

/*
 * Obtains a random number in the range (0, max]. 
 */
uint32_t random_range (uint32_t max)
{
	uint64_t x;
	x = generate_random ();
	x = x % max;
	return (uint32_t) x;
}

/*
 * Writes ther entropy pool out to disk. 
 */
void finalize_random (char const * const home)
{
	char random_pool_path [32];
	int fd;
	strncpy (random_pool_path, home, 32);
	strncat (random_pool_path, RANDOM_POOL, 32);
	fd = open (random_pool_path, O_WRONLY | O_TRUNC | O_CREAT, 0644);
	write (fd, entropy_pool, sizeof (entropy_pool));
	close (fd);
}


